const request = require('request');
const moment = require('moment');
const wopenssl = require('wopenssl');
const BigInt = require('big-integer');
const crypto = require('crypto');


class Unionpay {
  constructor(config) {
    const {
      frontUrl,
      backUrl,
      merId,
      certId,
      privateKey,
      publicKey,
      frontReqUrl,
      queryTransUrl,
    } = config;
    this._config = Object.assign({}, {
      frontUrl,
      backUrl,
      merId,
      certId,
      privateKey,
      publicKey,
      frontReqUrl,
      queryTransUrl,
    });
  }
  
  createLinkString(params, encode, status) {
    let ks;
    let str = '';
    if (status === true) {
      ks = Object.keys(params).sort();
    } else {
      ks = Object.keys(params);
    }
    for (let i = 0; i < ks.length; i += 1) {
      let k = ks[i];
      if (encode === true) {
        k = encodeURIComponent(k);
      }
      if (str.length > 0) {
        str += '&';
      }
      if (k !== null && k !== undefined && k !== '') { // 如果参数的值为空不参与签名；
        str += `${k}=${params[k]}`;
        // str = str + k + '=' + params[k];
      }
    }
    return str;
  }

  transferParams(params) {
    const newParams = {};
    if (typeof params !== 'object') {
      return false;
    }
    for (let i = 0; i < params.length; i += 1) {
      const items = params[i].split('=');
      const key = items[0];
      const value = items[1];
      newParams[key] = value;
    }
    return newParams;
  }

  filterPara(params) {
    const obj = {};
    Object.keys(params).forEach((k) => {
      const newK = k;
      if (newK !== 'signature' && params[k]) {
        obj[k] = params[k];
      }
    });
    return obj;
  }

  /**
   * 验签
   * @param {*} params 
   */
  verify(params) {// 验签
    // 提供校验数据
    const signatureStr = params.signature;
    const params1 = this.filterPara(params);
    const prestr = this.createLinkString(params1, false, true);
    const publicKey = params.signPubKeyCert;
    // 以下部分为公钥验签名
    const sha1 = crypto.createHash('sha256');
    sha1.update(prestr, 'utf8');
    const ss1 = sha1.digest('hex');
    // 公钥验签
    const verifier = crypto.createVerify('sha256');
    verifier.update(ss1);
    const verifyResult = verifier.verify(publicKey, signatureStr, 'base64');
    return verifyResult;
  }

   /**
   * @description  生产签名(算法是sha256)
   * @param {Object} params       -签名所需要的参数
   * @param {String} privateKey   -签名所需要的私钥
   */
  signatureGenerate(params, privateKey) { //生产签名
    const newObj = params;
    if (Object.prototype.toString(params) === '[object Object]' && typeof privateKey === 'string') {
      const prestr = this.createLinkString(params, true, true);
      const sha1 = crypto.createHash('sha256');
      sha1.update(prestr, 'utf8');
      const ss1 = sha1.digest('hex');
      // 私钥签名
      const sign = crypto.createSign('sha256');
      sign.update(ss1);
      const sig = sign.sign(privateKey, 'base64');
      newObj.signature = sig;
    } else {
      return false;
    }
    return newObj;
  }

  /**
   * @description 用来验证银联回调签名
   * @param {Object} params    - 支付成功之后银联回调的数据
   */
  verifyCallback(params) { //验证银联回调签名
    this.result = this.verify(params);
    return this.result;
  }



  hexToDecimal(hexStr) {
    return BigInt(hexStr, 16).toString();
  }

  /**
   * @function parseSignedDataFromPfx
   * @description 获取签名数据
   * @param {string} path - 证书文件.pfx的路径
   * @param {string} password - 解析.pfx文件所需的密码，默认为000000
   * @return {{certificate: *, privateKey: *}}
   */
  parseSignedDataFromPfx(path, password) { // 解析.pfx格式的证书 获取签名数据certificate   privateKey
    const extractedData = wopenssl.pkcs12.extract(path, password);
    return {
      certificate: extractedData.certificate,
      privateKey: extractedData.rsa,
    };
  }

  /**
   * @function parseCertData
   * @description Decrypting encryption data from certificate data.
   * @param {string} certificate - 证书数据
   * @param certificate
   * @return {Object}
   */
  parseCertData(certificate) {  // 获取签名数据 certId
    const certData = wopenssl.x509.parseCert(certificate);
    const certId = this.hexToDecimal(certData.serial);
    return certId;
  }

  /**
  * @description 创建支付订单的操作
  * @param {String} orderIdNo   - 用户支付订单号
  * @param {String} txnAmtValue - 用户支付金额
  * @param {Object} options     - 附加信息，银联会原样返回，用于平台中的校验
  *
  */
  createOrder(goods) {  // 创建订单
    return new Promise((resolve, reject) => {
      const necessaryParams = {
        // https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=740&apiservId=568&version=V1.0&bussType=1
        // https://open.unionpay.com/tjweb/user/mchTest/testCaseDetail?bCId=1
        // 签名	signature	String(1,1024)		M-必填	 填写对报文摘要的签名
        // 签名方法	signMethod  填写对报文摘要的签名
        // 商户证书序列号	merCertId  填写商户签名私钥证书的Serial Number（十进制），该值可通过银联提供的SDK获取仅支持数字。
        // 随机字符串	nonceStr  String(1,32)
        // 业务接口	bizMethod	String(1,128)		M-必填	acp.trade.refund
        // 参数集合	bizContent	String(1,10240)		C-按条件必填	
        version: '5.1.0',  // 版本
        encoding: 'utf-8', // 编码
        signMethod: '01', 
        txnType: '01',  // 交易类型
        txnSubType: '01', // 交易子类
        bizType: '000201', // 产品类型  '000202'
        accessType: '0',  // 接入类型
        backUrl: this._config.backUrl,  // 后台通知地址
        frontUrl: this._config.frontUrl, // 前台通知地址
        currencyCode: '156',
        merId: this._config.merId,  // 商户代码 已被批准加入银联互联网系统的商户代码 固定15位长，仅支持数字和字母
        orderId: goods.out_trade_no, // 商户订单号 String(8，40)	仅支持数字和字母
        txnAmt: goods.total_amount,  // 交易金额
        txnTime: moment().format('YYYYMMDDHHmmss'),  // 交易发送时间 格式： yyyyMMDDHHmmss
        payTimeout: moment().add(10, 'minutes').format('YYYYMMDDHHmmss'),
        certId: this._config.certId,  // 商户号，已被批准加入银联互联网系统的商户代码
        channelType: '07',  // 渠道
        reqReserved: goods.options,  // 请求方自定义域 商户自定义保留域，交易应答时会原样返回
      };
      console.log(necessaryParams);
      console.log('----->>>>>>', necessaryParams);
      const result = this.signatureGenerate(necessaryParams, this._config.privateKey);
      try {
        request.post(this._config.frontReqUrl, { form: result }, (err, response, body) => {
          if (err) {
            throw (new Error(err));
          }
          console.log(body)
          return resolve(body);
        });
      } catch (ex) {
        return reject(new Error('生成订单时候发生错误'));
      }
      return true;
    });
  }

  /**
   * @description 查询订单交易状态的操作
   * @param {String} orderIdNo  - 用户支付订单号
   * @param {String} txnTime    - 订单提交到银联的时间，需要格式为YYYYMMDDHHmmss
   */
  queryOrder(goods) {
    return new Promise((resolve, reject) => {  // 查询订单
      const necessaryParams = {
        version: '5.1.0',
        encoding: 'utf-8',
        signMethod: '01',
        txnType: '00',
        txnSubType: '00',
        bizType: '000000',
        accessType: '0',
        channelType: '07',
        orderId: goods.out_trade_no,
        merId: this._config.merId,
        txnTime: goods.txnTime,  // 交易发送时间 格式： yyyyMMDDHHmmss
        certId: this._config.certId,
      };
      const result = this.signatureGenerate(necessaryParams, this._config.privateKey);
      try {
        request.post(this._config.queryTransUrl, { form: result }, (err, response, body) => {
          const splitedString = body.split('&');
          const paramsObject = this.transferParams(splitedString);
          console.log(paramsObject)
          return resolve(paramsObject);
        });
      } catch (ex) {
        return reject(new Error('查询订单时候发生错误'));
      }
      return true;
    });
  }
  
}

module.exports = Unionpay;
